import numpy as np
from mcts.base.base import BaseState,BaseAction
from mcts.searcher.mcts import MCTS
from collections import namedtuple
from copy import deepcopy

# 0 0 0 3 0 0 0
# 0 0 3 2 3 0 0
# 0 3 2 1 2 3 0
# 3 2 1 # 1 2 3
# 0 3 2 1 2 3 0
# 0 0 3 2 3 0 0
# 0 0 0 3 0 0 0

boardLength=7
still=True
AIplayer=0

move=[(-1,0),(1,0),(0,-1),(0,1)]

actionDict={'0':'不动','1':'左','2':'右','3':'上','4':'下',
            '1 1':'左左','2 2':'右右','3 3':'上上','4 4':'下下',
            '1 3':'左上','1 4':'左下','2 3':'右上','2 4':'右下',
            '3 1':'上左','4 1':'下左','3 2':'上右','4 2':'下右',
            '1 1 1':'左左左','2 2 2':'右右右','3 3 3':'上上上','4 4 4':'下下下',
            '1 1 3':'左左上','1 3 1':'左上左','3 1 1':'上左左',
            '1 1 4':'左左下','1 4 1':'左下左','4 1 1':'下左左',
            '2 2 3':'右右上','2 3 2':'右上右','3 2 2':'上右右',
            '2 2 4':'右右下','2 4 2':'右下右','4 2 2':'下右右',
            '3 3 1':'上上左','3 1 3':'上左上','1 3 3':'左上上',
            '3 3 2':'上上右','3 2 3':'上右上','2 3 3':'右上上',
            '4 4 1':'下下左','4 1 4':'下左下','1 4 4':'左下下',
            '4 4 2':'下下右','4 2 4':'下右下','2 4 4':'右下下',
            '1 3 2':'左上右','1 4 2':'左下右','2 3 1':'右上左','2 4 1':'右下左',
            '3 1 4':'上左下','3 2 4':'上右下','4 1 3':'下左上','4 2 3':'下右上'
            }

class Cell(object):
    def __init__(self,x,y,used=False):
        self.x=x
        self.y=y
        self.used=used
        self.player=0
        self.connect=[1,1,1,1]#左,右,上,下
        if self.x==0:
            self.connect[0]=0
        if self.x==boardLength-1:
            self.connect[1]=0
        if self.y==0:
            self.connect[2]=0
        if self.y==boardLength-1:
            self.connect[3]=0

    def __str__(self):
        if self.used:return '%s'%self.player
        return '0'

class Player(object):
    def __init__(self,id,x,y):
        self.id=id
        self.x=x
        self.y=y

cellss=[]
for i in range(boardLength):
    cells=[]
    for j in range(boardLength):
        cell=Cell(i,j)
        cells.append(cell)
    cellss.append(cells)

cellss[0][0].used=True
cellss[0][0].player=1
cellss[boardLength-1][boardLength-1].used=True
cellss[boardLength-1][boardLength-1].player=2
npCellss=np.array(cellss)

players=[Player(1,0,0),Player(2,boardLength-1,boardLength-1)]

class Action(BaseAction):
    def __init__(self,player,actionType,wallDirection):
        self.player=player
        self.actionType=actionType
        self.wallDirection=wallDirection

    def __str__(self):
        return str(self.actionType)+' '+str(self.wallDirection)

    def __repr__(self):
        return str(self)

    def __eq__(self,other):
        return self.__class__==other.__class__ \
               and self.actionType==other.actionType \
               and self.player==other.player \
               and self.wallDirection==other.wallDirection

    def __hash__(self):
        return hash((self.actionType,self.wallDirection,self.player))

class XuanQi(BaseState):
    def __init__(self):
        self.currentPlayer:int=0
        self.round:int=0
        self.board=npCellss
        self.players=players
        self.areas=np.array([0,0])

    def get_current_player(self) -> int:
        return self.currentPlayer

    def is_terminal(self) -> bool:
        return not self.bfsSearch(self.currentPlayer)

    def get_reward(self):
        self.bfsSearch(self.currentPlayer)
        self.bfsSearch(1-self.currentPlayer)
        return self.areas[AIplayer]

    def get_possible_actions(self):
        currentPlayer=self.currentPlayer
        possibleActions=[]
        if still:
            player=self.players[currentPlayer]
            for i in range(4):
                if self.board[player.x,player.y].connect[i]:
                    possibleActions.append(Action(player=currentPlayer,actionType='0',wallDirection=i))
        for actionStr in actionDict:
            if actionStr=='0':
                continue
            connect=self.doMove(currentPlayer,actionStr)
            if connect is False:
                continue
            for i in range(4):
                if connect[i]:
                    possibleActions.append(Action(player=currentPlayer,actionType=actionStr,wallDirection=i))
        return possibleActions

    def doMove(self,currentPlayer,actionStr:str):
        player=self.players[currentPlayer]
        x,y=player.x,player.y
        cell=self.board[x,y]
        if actionStr=='0':
            return cell.connect
        actionList=actionStr.split(' ')
        for action in actionList:
            i=int(action)-1
            if cell.connect[i]:
                x+=move[i][0]
                y+=move[i][1]
            else:return False
            cell=self.board[x,y]
            if cell.used:
                return False
        return cell.connect

    def bfsSearch(self,player) -> bool:
        players=self.players
        sx,sy=players[player].x,players[player].y
        tx,ty=players[1-player].x,players[1-player].y
        present: list[Cell]=[self.board[sx,sy]]
        visited=set()
        visited.add(self.board[sx,sy])
        while present:
            future: list[Cell]=[]
            for c in present:
                l=[(-1,0),(1,0),(0,-1),(0,1)]
                for i in range(4):
                    x,y=l[i]
                    newX,newY=c.x+x,c.y+y
                    if newX<0 or newY<0 or newX>=boardLength or newY>=boardLength:
                        continue
                    newCell=self.board[newX,newY]
                    if newCell in visited:
                        continue
                    if c.connect[i]:
                        visited.add(newCell)
                        future.append(newCell)
            present=future
        self.areas[player]=len(visited)
        if self.board[tx,ty] in visited:
            return True
        return False

    def take_action(self,action:Action):
        newState=deepcopy(self)
        currentPlayer=action.player
        actionStr=action.actionType
        wallDirection=action.wallDirection
        player=newState.players[currentPlayer]
        x,y=player.x,player.y
        cell=newState.board[x,y]
        actionList=actionStr.split(' ')
        for action in actionList:
            i=int(action)-1
            if i==-1:
                break
            if cell.connect[i]:
                x+=move[i][0]
                y+=move[i][1]
            cell=newState.board[x,y]
            if cell.used:
                raise Exception('You cannot step onto your opponent!')
        newState.board[player.x,player.y].used=False
        newState.board[player.x,player.y].player=0
        newState.board[x,y].used=True
        newState.board[x,y].player=currentPlayer+1
        newState.board[x,y].connect[wallDirection]=0
        newState.players[currentPlayer]=Player(currentPlayer,x,y)

        if wallDirection in [0,1]:
            otherDirection=1-wallDirection
        else:
            otherDirection=5-wallDirection

        _x,_y=move[wallDirection]
        ox,oy=x+_x,y+_y
        if 0<=ox<boardLength and 0<=oy<boardLength:
            newState.board[ox,oy].connect[otherDirection]=0

        newState.currentPlayer=1-currentPlayer
        newState.round+=1
        return newState

    def printBoard(self):
        board=self.board.transpose()
        row='+'
        for i in range(boardLength):
            row+='---+'
        print(row)
        for i in range(boardLength):
            row1='|'
            row2='+'
            for j in range(boardLength):
                cell=board[i,j]
                row1+=' '+str(cell)+' '
                row1+=' ' if cell.connect[1] else '|'
                row2+='   +' if cell.connect[3] else '---+'
            print(row1)
            print(row2)

initState=XuanQi()

def interface(rounds,initState):
    state=initState
    newType=['','']
    newWall=[0,0]
    for i in range(rounds):
        state.printBoard()
        newType[0]='114514'
        newWall[0]=-1
        while newType[0]=='114514':
            newType[0]=input('玩家1，请输入您要采取的动作，1左2右3上4下，最多输入三次，请用空格隔开')
            if newType[0] not in actionDict:
                print('输入错误！')
                newType[0]='114514'
            if state.doMove(0,newType[0]) is False:
                print('无法抵达！')
                newType[0]='114514'
        while newWall[0]<0:
            newWall[0]=int(input('玩家1，请输入您要建墙的方向，1左2右3上4下'))-1
            if state.doMove(0,newType[0])[newWall[0]]==0:
                print('无法造墙！')
                print(initState.doMove(0,newType[0])[newWall[0]])
                newWall[0]=-1
        state=state.take_action(Action(0,newType[0],newWall[0]))

        if state.bfsSearch(state.currentPlayer) is False:
            state.bfsSearch(1-state.currentPlayer)
            print('游戏结束，正在结算！')
            if state.areas[0]>state.areas[1]:
                print('玩家1胜利！')
            elif state.areas[0]<state.areas[1]:
                print('玩家2胜利！')
            else:
                print('平局！')
            return 0

        state.printBoard()
        newType[1]='114514'
        newWall[1]=-1
        while newType[1]=='114514':
            newType[1]=input('玩家2，请输入您要采取的动作，1左2右3上4下，最多输入三次，请用空格隔开')
            if newType[1] not in actionDict:
                print('输入错误！')
                newType[1]='114514'
            if state.doMove(1,newType[1]) is False:
                print('无法抵达！')
                newType[1]='114514'
        while newWall[1]<0:
            newWall[1]=int(input('玩家2，请输入您要建墙的方向，1左2右3上4下'))-1
            if state.doMove(1,newType[1])[newWall[1]]==0:
                print('无法造墙！')
                newWall[1]=-1
        state=state.take_action(Action(1,newType[1],newWall[1]))

        if state.bfsSearch(state.currentPlayer) is False:
            state.bfsSearch(1-state.currentPlayer)
            print('游戏结束，正在结算！')
            if state.areas[0]>state.areas[1]:
                print('玩家1胜利！')
            elif state.areas[0]<state.areas[1]:
                print('玩家2胜利！')
            else:
                print('平局！')
            return 0

def AI_interface(rounds,initState):
    searcher=MCTS(time_limit=12000)
    state=initState
    newType=['','']
    newWall=[0,0]
    for i in range(rounds):
        state.printBoard()
        bestAction,details=searcher.search(initial_state=state,needDetails=True)
        print('玩家1思考完毕')
        print(details)
        newType[0],newWall[0]=bestAction.actionType,bestAction.wallDirection
        state=state.take_action(Action(0,newType[0],newWall[0]))

        if state.bfsSearch(state.currentPlayer) is False:
            state.bfsSearch(1-state.currentPlayer)
            print('游戏结束，正在结算！')
            state.printBoard()
            if state.areas[0]>state.areas[1]:
                print('玩家1胜利！')
            elif state.areas[0]<state.areas[1]:
                print('玩家2胜利！')
            else:
                print('平局！')
            return 0

        state.printBoard()
        newType[1]='114514'
        newWall[1]=-1
        while newType[1]=='114514':
            newType[1]=input('玩家2，请输入您要采取的动作，1左2右3上4下，最多输入三次，请用空格隔开')
            if newType[1] not in actionDict:
                print('输入错误！')
                newType[1]='114514'
            if state.doMove(1,newType[1]) is False:
                print('无法抵达！')
                newType[1]='114514'
        while newWall[1]<0:
            newWall[1]=int(input('玩家2，请输入您要建墙的方向，1左2右3上4下'))-1
            if state.doMove(1,newType[1])[newWall[1]]==0:
                print('无法造墙！')
                newWall[1]=-1
        state=state.take_action(Action(1,newType[1],newWall[1]))

        if state.bfsSearch(state.currentPlayer) is False:
            state.bfsSearch(1-state.currentPlayer)
            print('游戏结束，正在结算！')
            state.printBoard()
            if state.areas[0]>state.areas[1]:
                print('玩家1胜利！')
            elif state.areas[0]<state.areas[1]:
                print('玩家2胜利！')
            else:
                print('平局！')
            return 0

def AIvsAI_interface(rounds,initState):
    global AIplayer
    searcher=MCTS(time_limit=12000)
    state=initState
    newType=['','']
    newWall=[0,0]
    for i in range(rounds):
        AIplayer=0
        state.printBoard()
        bestAction,details=searcher.search(initial_state=state,needDetails=True)
        print('玩家1思考完毕')
        print(details)
        newType[0],newWall[0]=bestAction.actionType,bestAction.wallDirection
        state=state.take_action(Action(0,newType[0],newWall[0]))

        if state.bfsSearch(state.currentPlayer) is False:
            state.bfsSearch(1-state.currentPlayer)
            print('游戏结束，正在结算！')
            state.printBoard()
            if state.areas[0]>state.areas[1]:
                print('玩家1胜利！')
            elif state.areas[0]<state.areas[1]:
                print('玩家2胜利！')
            else:
                print('平局！')
            return 0

        AIplayer=1
        state.printBoard()
        bestAction,details=searcher.search(initial_state=state,needDetails=True)
        print('玩家2思考完毕')
        print(details)
        newType[1],newWall[1]=bestAction.actionType,bestAction.wallDirection
        state=state.take_action(Action(1,newType[1],newWall[1]))

        if state.bfsSearch(state.currentPlayer) is False:
            state.bfsSearch(1-state.currentPlayer)
            print('游戏结束，正在结算！')
            state.printBoard()
            if state.areas[0]>state.areas[1]:
                print('玩家1胜利！')
            elif state.areas[0]<state.areas[1]:
                print('玩家2胜利！')
            else:
                print('平局！')
            return 0

if __name__ == '__main__':
    #interface(50,initState)
    AI_interface(50,initState)
    #AIvsAI_interface(boardLength**2,initState)